commonlibsse_ng\skse\interfaces/
serialization.rs

1// C++ Original code
2// - ref: https://github.com/SARDONYX-forks/CommonLibVR/blob/ng/include/SKSE/Interfaces.h
3// - ref: https://github.com/SARDONYX-forks/CommonLibVR/blob/ng/src/SKSE/Interfaces.cpp
4// SPDX-FileCopyrightText: (C) 2018 Ryan-rsm-McKenzie
5// SPDX-License-Identifier: MIT
6//
7// SPDX-FileCopyrightText: (C) 2025 SARDONYX
8// SPDX-License-Identifier: Apache-2.0 OR MIT
9
10use core::ffi::c_void;
11
12use crate::re::BSCoreTypes::{FormID, VMHandle};
13use crate::skse::api::{ApiStorageError, get_plugin_handle};
14use crate::skse::impls::stab::SKSESerializationInterface;
15
16const U32_MAX: usize = u32::MAX as usize;
17
18/// Interface for interacting with SKSE's serialization functions.
19///
20/// This struct provides methods for interacting with serialization operations
21/// such as setting unique IDs, handling callbacks for form deletions and loads,
22/// and reading/writing record data.
23#[derive(Debug, Clone)]
24pub struct SerializationInterface(&'static SKSESerializationInterface);
25
26impl SerializationInterface {
27    /// The version number of the serialization interface.
28    pub const VERSION: u32 = 4;
29
30    /// Creates a new `SerializationInterface` instance.
31    #[inline]
32    pub(crate) const fn new(interface: &'static SKSESerializationInterface) -> Self {
33        Self(interface)
34    }
35
36    /// Returns the version of the serialization interface.
37    #[inline]
38    pub const fn version(&self) -> u32 {
39        self.0.version
40    }
41
42    /// Sets a unique identifier for the serialization interface.
43    ///
44    /// # Errors
45    /// Returns an error if the internal global API storage is uninitialized
46    /// (e.g., if `skse::init` has not been called).
47    #[inline]
48    pub fn set_unique_id(&self, uid: u32) -> Result<(), ApiStorageError> {
49        unsafe { (self.0.SetUniqueId)(get_plugin_handle()?, uid) };
50        Ok(())
51    }
52
53    /// Sets a callback function that will be called when a form is deleted.
54    ///
55    /// # Errors
56    /// Returns an error if the internal global API storage is uninitialized
57    /// (e.g., if `skse::init` has not been called).
58    #[inline]
59    pub fn set_form_delete_callback(
60        &self,
61        callback: fn(handle: VMHandle),
62    ) -> Result<(), ApiStorageError> {
63        #[allow(clippy::fn_to_numeric_cast_any)]
64        let void_f = (callback as *mut fn(handle: VMHandle)).cast();
65        unsafe { ((self.0).SetFormDeleteCallback)(get_plugin_handle()?, void_f) }
66
67        Ok(())
68    }
69
70    /// Sets a callback function that will be called when the plugin is loaded.
71    ///
72    /// # Errors
73    /// Returns an error if the internal global API storage is uninitialized
74    /// (e.g., if `skse::init` has not been called).
75    #[inline]
76    pub fn set_load_callback(&self, callback: fn(&Self)) -> Result<(), ApiStorageError> {
77        #[allow(clippy::fn_to_numeric_cast_any)]
78        let void_f = (callback as *mut fn(&Self)).cast();
79        unsafe { ((self.0).SetLoadCallback)(get_plugin_handle()?, void_f) }
80        Ok(())
81    }
82
83    /// Sets a callback function that will be called when the plugin is reverted.
84    ///
85    /// # Errors
86    /// Returns an error if the internal global API storage is uninitialized
87    /// (e.g., if `skse::init` has not been called).
88    #[inline]
89    pub fn set_revert_callback(&self, callback: fn(&Self)) -> Result<(), ApiStorageError> {
90        #[allow(clippy::fn_to_numeric_cast_any)]
91        let void_f = (callback as *mut fn(&Self)).cast();
92        unsafe { ((self.0).SetRevertCallback)(get_plugin_handle()?, void_f) }
93        Ok(())
94    }
95
96    /// Sets a callback function that will be called when the plugin is saved.
97    ///
98    /// # Errors
99    /// Returns an error if the internal global API storage is uninitialized
100    /// (e.g., if `skse::init` has not been called).
101    #[inline]
102    pub fn set_save_callback(&self, callback: fn(&Self)) -> Result<(), ApiStorageError> {
103        #[allow(clippy::fn_to_numeric_cast_any)]
104        let callback = (callback as *mut fn(&Self)).cast();
105        unsafe { ((self.0).SetSaveCallback)(get_plugin_handle()?, callback) }
106        Ok(())
107    }
108
109    /// Writes a record to the serialization interface.
110    ///
111    /// # Errors
112    /// Returns an error if the write operation fails or if the buffer length exceeds `u32::MAX`.
113    #[inline]
114    pub fn write_record<T>(&self, record_type: u32, version: u32, buf: &T) -> Result<(), Error> {
115        let data_size: usize = core::mem::size_of::<T>();
116
117        if data_size > U32_MAX {
118            return Err(Error::TooLargeWriteRecordData { actual: data_size });
119        }
120
121        let void_buf = (buf as *const T).cast::<c_void>();
122        let result =
123            unsafe { ((self.0).WriteRecord)(record_type, version, void_buf, data_size as u32) };
124
125        if result { Ok(()) } else { Err(Error::WriteRecordError) }
126    }
127
128    /// Opens a record for writing.
129    ///
130    /// # Errors
131    /// Returns an error if the open operation fails.
132    #[inline]
133    pub fn open_recode(&self, record_type: u32, version: u32) -> Result<(), Error> {
134        if unsafe { ((self.0).OpenRecord)(record_type, version) } {
135            Ok(())
136        } else {
137            Err(Error::OpenRecordError)
138        }
139    }
140
141    /// Writes record data to the serialization interface.
142    ///
143    /// # Errors
144    /// Returns an error if the write operation fails or if the buffer length exceeds `u32::MAX`.
145    #[inline]
146    pub fn write_record_data<T>(&self, buf: &[T]) -> Result<(), Error> {
147        let buf_len = buf.len();
148
149        match buf_len {
150            0 => Ok(()),
151            1..=U32_MAX => {
152                let result =
153                    unsafe { ((self.0).WriteRecordData)(buf.as_ptr().cast(), buf_len as u32) };
154                if result { Ok(()) } else { Err(Error::WriteRecordDataError) }
155            }
156            too_large_size => Err(Error::TooLargeWriteRecordData { actual: too_large_size }),
157        }
158    }
159
160    /// Reads record data into the provided buffer.
161    ///
162    /// # Returns
163    /// The number of bytes read.
164    #[inline]
165    pub fn read_record_data<T>(&self, buf: &mut [T]) -> u32 {
166        unsafe { (self.0.ReadRecordData)(buf.as_mut_ptr().cast(), buf.len() as u32) }
167    }
168
169    /// Retrieves the next record's type, version, and length.
170    ///
171    /// # Errors
172    /// Returns an error if the operation fails.
173    #[inline]
174    pub fn get_next_record_info(
175        &self,
176        record_type: &mut u32,
177        version: &mut u32,
178        length: &mut u32,
179    ) -> Result<(), Error> {
180        unsafe {
181            let result = (self.0.GetNextRecordInfo)(record_type, version, length);
182            if result { Ok(()) } else { Err(Error::GetNextRecordInfoError) }
183        }
184    }
185
186    /// Resolves the new form ID based on an old form ID.
187    ///
188    /// # Errors
189    /// Returns an error if the form ID resolution fails.
190    #[inline]
191    pub fn resolve_form_id(&self, old: FormID, new: &mut FormID) -> Result<(), Error> {
192        unsafe {
193            let result = (self.0.ResolveFormId)(old.get(), &mut new.get());
194            if result { Ok(()) } else { Err(Error::ResolveFormIdError) }
195        }
196    }
197
198    /// Resolves the new handle based on an old handle.
199    ///
200    /// # Errors
201    /// Returns an error if the handle resolution fails.
202    #[inline]
203    pub fn resolve_handle(&self, old: VMHandle, new: &mut VMHandle) -> Result<(), Error> {
204        let result = unsafe { (self.0.ResolveHandle)(old.get(), &mut new.get()) };
205        if result { Ok(()) } else { Err(Error::ResolveHandleError) }
206    }
207}
208
209/// Custom error type for serialization-related failures.
210#[derive(Debug, snafu::Snafu)]
211pub enum Error {
212    /// Failed to write record.
213    WriteRecordError,
214
215    /// Failed to write record data.
216    WriteRecordDataError,
217
218    /// The buffer size exceeds the maximum allowed (`u32::MAX`).
219    TooLargeWriteRecordData { actual: usize },
220
221    /// Failed to open record.
222    OpenRecordError,
223
224    /// Failed to get next record info.
225    GetNextRecordInfoError,
226
227    /// Failed to resolve form ID.
228    ResolveFormIdError,
229
230    /// Failed to resolve handle.
231    ResolveHandleError,
232}